import kt.RemoteDB;
import kt.Manager;
import kt.Object;

class App {

	/*
	 * This simple class performs a series of tests
	 * 
	 */
	public static function main() {
		var db : kt.RemoteDB = new kt.RemoteDB();
		db.open(); // Don't forget to start your server before with `ktserver command`
		// db.setTarget("test.kch"); // use a database called test.kch on localhost `ktserver test.kch` 
		db.clear();
		db.set(	"foo","bar" );

		// ------------------ ADD

		trace( db.add("test", "toto") ) ; // true
		trace( db.add("test", "toto", 30) ); // false, test already exists
		trace( db.add("titi", "toto", 30) ); // true

		// ------------------ REPLACE

		trace( db.replace("titi", "tata", 30) ); // true
		trace( db.replace("poum", "tata") ); // false, poum doesn't exist
		
		// ------------------ APPEND
		
		trace( db.append("titi", "titi") ); // true-> titi = tatatiti
		trace( db.append("poum", "titi") ); // false

		// ------------------ INCREMENT
		
		trace( db.increment("inc",1) ); // -> set record to 1
		trace( db.increment("inc",2) ); // -> set record to 3
		trace( db.get("inc") ); // does not work return null
		trace( db.increment("inc") ); // return 3
		trace(db.increment("abc", 1, 1000));  //sets 1001
		trace(db.increment("abc", 1, 1001));  // sets 1001
		trace(db.increment("xyz", 1, -1/0)); //  fails if "xyz" is missing : return 0
		trace(db.increment("xyz", 1));       //  sets 1
		trace(db.increment("xyz", 1, -1/0)); //  sets 2 because "xyz" exists
		trace(db.increment("xyz", 0, -1/0)); // gets the current value or fails if missing : return 0
		trace(db.increment("xyz", 100, 1/0));  //sets 100 certainly
		trace(db.increment("xyz", 77, 1/0));   //sets 77 certainly

		// ------------------ INCREMENT DOUBLE

		trace( db.incrementDouble("incD",0.3) ); // -> set record to 0.3
		trace( db.incrementDouble("incD",2.6) ); // -> set record to 2.9
		trace( db.get("incD") ); // does not work return null
		trace( db.incrementDouble("incD") ); // return 2.9
		trace(db.incrementDouble("fabc", 1, 1000));  //sets 1001.0
		trace(db.incrementDouble("fabc", 1, 1001));  // sets 1002.0
		trace(db.incrementDouble("fxyz", 1));       //  sets 1.0

		/* is not handled yet in Kyoto products
		trace(db.incrementDouble("fxyz", 1, -1/0)); //  fails if "fxyz" is missing
		trace(db.incrementDouble("fxyz", 1, -1/0)); //  sets 2.0 because "fxyz" exists
		trace(db.incrementDouble("fxyz", 0, -1/0)); // gets the current value or fails if missing
		trace(db.incrementDouble("fxyz", 100, 1/0));  //sets 100.0 certainly
		trace(db.incrementDouble("fxyz", 77, 1/0));   //sets 77.0 certainly
		*/

		// ------------------ CAS ( Compare & swap )

		db.set("color", "red");
		trace( db.cas( "color", "blue", "green" ) ); // false : old value should be red not blue. the record will not be modified
		trace( db.cas( "color", "red", "green" ) ); // true : old value was red, so the record values is updated to green.
		
		// ------------------ REMOVE
		
		trace( db.remove("unknown" ) ); // false : key is unknown
		trace( "count before removal :" +  db.count() ); 
		trace( db.remove("color") ); // true, record exists
		trace( "count after removal :" +  db.count() );

		// ------------------ MATCH PREFIX

		db.set("user_1", "toto" );
		db.set("user_2", "titi" );
		db.set("user_3", "tutu" );
		trace( db.matchPrefix( "user_" ) ); // returns user_1, user_2, user_3

		// ------------------ MATCH REGEX

		db.set("japtok", "japon_tokyo" );
		db.set("japkyot", "japon_kyoto" );
		db.set("fp", "france_paris" );
		trace( db.matchRegex( "jap" ) ); // returns japtok, japkyot

		// ------------------ SIZE

		trace( "size=" + db.size() );

		// ------------------ SET TARGET

		db.setTarget( "test.kch" );
		db.set("japkyot", "japon_kyoto" ); // return false : db is not known (unless you have a test.kch base opened on default port)
		db.setTarget( "" );
		db.set("japkyot", "japon_kyoto" ); // return true : standard db used
		
		// ------------------ BULK OPERATIONS
		
		var hInfos = new Hash();
		hInfos.set( "cust_1", "Henry" );
		hInfos.set( "cust_2", "Marc" );
		hInfos.set( "cust_3", "Judith" );
		hInfos.set( "france", "marseille" );
		trace( db.setBulk( hInfos ) ); // returns 4
		trace( db.get( "france" ) ); // returns marseille

		var keysToRemove = ["france","cust_2","japkyot","unknown"];
		trace( db.removeBulk( keysToRemove ) ); // returns 3 because "unknown" record was not found

		var keys = ["cust_1", "cust_3"];
		trace( db.getBulk( keys ) ); //  returns cust_3=>Judith, cust_1=>Henry

		// ------------------ BINARY BULK OPERATIONS

		// set records using BulkRecord
		var a = new Array();
		for (i in 0...100 ) {
			var b : kt.BulkRecord = {key:"br_"+i, value:i + "", expirationTime:0, dbidx:0};
			a.push( b );
		}
		trace( db.setBulkBinary(a) ); // returns 100
		trace( db.get("br_2") ); // returns 2
		trace( db.getBulkBinary( ["br_2"] ) ); // returns filled BulkRecord  
		trace( db.removeBulkBinary( ["br_2"] ) ); // returns 1
		trace( db.removeBulkBinary( ["br_2"] ) ); // returns -1

		// ------------------ CURSORS

		db.clear();
		db.set( "alpha", "foo1" ); 
		db.set( "beta", "foo2" ); 
		db.set( "lambda", "foo3" ); 
		db.set( "omega", "foo4" ); 

		var cur = db.cursor();
		trace( cur.getKey() ); // return null, need to jump to the beggining
		trace( cur.jump() );  // 
		trace( cur.getKey() ); // first record found ( note : this may not be the first record you set. Please refer to the Cursor documentation.
		cur.step(); // step forward
		trace( cur.getKey() ); 
		trace( cur.getValue() ); 
		trace( cur.get() ); // returns the pair key/value
		cur.stepBack(); // step backward
		trace( cur.get() ); // returns the pair key/value
		cur.jumpTo( "theta" ); 
		trace( cur.get() ); // return null, the record does not exist
		cur.jumpTo("lambda");
		trace( cur.get() ); // return the the pair key/value
		trace( cur.remove() );	// 
		cur.jumpTo("omega");
		cur.setValue( "yesss!" );
		trace( cur.getValue() ); // trace yesss!

		// now we can also trasverse the records
		cur.jump();
		while( cur.next() ) {
			trace( cur.info );
		}

		// ------------------ MISC

		//trace( db.vacuum( 3 ) );
		trace( db.expression() ); // return :1978 (standard rpc port for kt com)
		trace( db.status() );
		//db.synchronize();


		// ------------------ kt.Manager & kt.Object
		// Note : A complete documentation will be available
		// when this part of the library will be finished
		// Don't use it on production as many changes may still occur
	
		kt.Manager.setConnection( db );
		var num = 10;
		for( i in 0...num ) {
			var o = new User();
			o.name = "user_" + (i + 1);
			o.age = 25;
	//		trace (o.delete() ); // return false
			o.insert();
		}
		trace( db.get("User:5:age") );

		var test_u : User = new User();
		test_u.name = "Jack Johnson";
		test_u.insert();
		var k : String = test_u.key;
		trace( User.manager.get(k) );
		var ki : Int = Std.parseInt(k);
		trace( User.manager.get( ki ) ); // same : keys work either with int or string when you don't provide any key

		var u : User = User.manager.get( 5 );
		trace( u.age );
		u.name = "jack";
		u.update();
		trace( User.manager.get(6) );

		u= User.manager.get( 6 );
		trace( u );
		trace( u.private_data );
		trace( u.friends );
		u.name = "henry";
		u.update();

		trace( User.manager.get( 5 ) );
		var u = User.manager.get( 6 );
		u.delete();

		try {
			u.update();
		} catch( e : Dynamic ) {
			trace( e.toString() );
		}

		var u = User.manager.get( 6 );
		trace( u ); // null : was previously deleted 

		var u = User.manager.get( 2 );
		var s : Session = new Session( u ); // key is provided in the constructor
		s.insert();
		trace( s );

		db.close();
	}
}

class User extends kt.Object {

	public static var PRIVATE_FIELDS = ["private_data","friends"];
	public static var manager = new UserManager();

	public var private_data : String;
	public var name : String;
	public var age : Int;
	public var creationDate : Date;
	public var money : Float;
	public var happy : Bool;

	public var friendInfo : String;
	public var friends : Array<Int>;

	public function new() {
		super();
		creationDate = Date.now();
		money = Std.random(10000) + 0.7;
		happy = Std.random(2) == 1;
		friends = [1,2,3,3,7867];
	}
}

class UserManager extends kt.Manager<User> {

	public function new(){
		super(User);
	}

	override function make( u : User ) {
		u.private_data = "This is private !";
		if( u.friendInfo == null || u.friendInfo.length <=0 ) return;
		u.friends = cast u.friendInfo.split(":");
	}

	override function unmake( u : User ) {
		if( u.friends == null || u.friends.length <=0 ) return;
		u.friendInfo = u.friends.join(":");
	}
}

class Session extends kt.Object {

	static var CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

	public static var manager : kt.Manager<Session>= new kt.Manager<Session>(Session);

	public var userKey : String;

	public function new( u : User ) {
		super();
		userKey = u.key;
		key = makeId(20);
	}

	function makeId(n) : String {
		var nchars = CHARS.length;
		var k = "";
		for (i in 0 ... n){
			k += CHARS.charAt( Std.int(Math.random() * nchars) );
		}
		return k;
	}
}
